package com.dasberg.gwt.domain.hibernate;

import com.dasberg.gwt.guice.AuthenticationTestModule;
import com.google.inject.AbstractModule;
import org.dbunit.DatabaseUnitException;
import org.dbunit.database.DatabaseConfig;
import org.dbunit.database.DatabaseConnection;
import org.dbunit.dataset.DataSetException;
import org.dbunit.dataset.IDataSet;
import org.dbunit.dataset.ReplacementDataSet;
import org.dbunit.dataset.xml.FlatXmlDataSet;
import org.dbunit.operation.DatabaseOperation;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.EntityManagerFactoryImpl;
import org.junit.After;
import org.junit.Before;

import javax.persistence.Persistence;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.SQLException;
import java.util.Properties;

import static org.junit.Assert.assertNotNull;

/**
 * Base test class for tests that need a hibernate session.
 * The transaction is always rolledback in the teardown.
 * @author mischa
 */
public abstract class DBProviderTestCase {

    private AbstractModule module;

    protected Session session;
    private SessionFactory sessionFactory;

    @Before
    public final void setUp() throws Exception {
        module = new AuthenticationTestModule();

        try {
            Properties properties = new Properties();
            properties.load(DBProviderTestCase.class.getResourceAsStream("/auth-persistence.properties"));
            EntityManagerFactoryImpl entityManagerFactory = (EntityManagerFactoryImpl) Persistence.createEntityManagerFactory("auth", properties);
            sessionFactory = entityManagerFactory.getSessionFactory();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        session = sessionFactory.openSession();
        session.getTransaction().begin();
        insertDatabaseDump();
        onSetup();
    }

    public abstract void onSetup();

    private void insertDatabaseDump() throws SQLException, DatabaseUnitException, IOException {
        DatabaseConnection connection = new DatabaseConnection(session.connection());
        DatabaseConfig config = connection.getConfig();
        config.setProperty(DatabaseConfig.PROPERTY_DATATYPE_FACTORY, new H2DataTypeFactory());
        // Disable Foreign key constraints for insertion.
        connection.getConnection().prepareStatement("set referential_integrity FALSE").execute();
        DatabaseOperation.CLEAN_INSERT.execute(connection, getFlatXmlDataSet());
        // Enable Foreign key constraints after insertion.
        connection.getConnection().prepareStatement("set referential_integrity TRUE").execute();
    }

    @After
    public final void tearDown() {
        onTearDown();
        session.close();
        sessionFactory.close();
    }

    public abstract void onTearDown();

    /**
     * Returns a FlatXmlDataset using the given resourceName.
     * @return the DataSet
     * @throws java.io.IOException
     * @throws org.dbunit.dataset.DataSetException
     */
    protected IDataSet getFlatXmlDataSet() throws IOException, DataSetException {
        InputStream is = getClass().getResourceAsStream(getDataset());
        assertNotNull("Resource " + getDataset() + " not found on the classpath.", is);
        IDataSet dataSet = new ReplacementDataSet(new FlatXmlDataSet(is));
        // Change [NULL] to null
        ((ReplacementDataSet) dataSet).addReplacementObject("[NULL]", null);
        return dataSet;
    }

    public void export() throws SQLException, IOException, DataSetException {
        IDataSet fullDataSet = new DatabaseConnection(session.connection()).createDataSet();
        FlatXmlDataSet.write(fullDataSet, new FileOutputStream("export.xml"));
    }

    protected void processSession() {
        session.flush();
        session.clear();
    }

    public abstract String getDataset();

}
